<template>
  <!-- 第一步，通过流程定义的列表，选择对应的流程 -->
  <template v-if="!startProcessDefinition">
    <el-input
      v-model="searchName"
      class="!w-50% mb-15px"
      placeholder="请输入流程名称"
      clearable
      @input="handleQuery"
      @clear="handleQuery"
    >
      <template #prefix>
        <Icon icon="ep:search" />
      </template>
    </el-input>
    <ContentWrap
      :class="{ 'process-definition-container': filteredProcessDefinitionList?.length }"
      class="position-relative pb-20px process-definition-main"
      v-loading="loading"
    >
      <el-row v-if="filteredProcessDefinitionList?.length" :gutter="20" class="!flex-nowrap">
        <el-col :span="5">
          <div class="flex flex-col">
            <div
              v-for="category in availableCategoryList"
              :key="category.code"
              class="flex items-center p-10px cursor-pointer text-14px rounded-md"
              :class="categoryActive.code === category.code ? 'text-#3e7bff bg-#e8eeff' : ''"
              @click="handleCategorySelect(category)"
            >
              {{ category.name }}
            </div>
          </div>
        </el-col>
        <el-col :span="19">
          <el-scrollbar ref="scrollWrapper" class="main-scroll-area" @scroll="handleScroll">
            <div
              class="mb-20px pl-10px"
              v-for="(definitions, categoryCode) in processDefinitionGroup"
              :key="categoryCode"
              :ref="`category-${categoryCode}`"
            >
              <h3 class="text-18px font-bold mb-10px mt-5px">
                {{ getCategoryName(categoryCode) }}
              </h3>
              <div class="grid grid-cols-3 gap3">
                <el-tooltip
                  v-for="definition in definitions"
                  :key="definition.id"
                  :content="definition.description"
                  :disabled="!definition.description || definition.description.trim().length === 0"
                  placement="top"
                >
                  <el-card
                    shadow="hover"
                    class="cursor-pointer definition-item-card"
                    @click="handleStartProcess(definition)"
                  >
                    <template #default>
                      <div class="flex">
                        <el-image
                          v-if="definition.icon"
                          :src="s_file_url(definition.icon)"
                          class="w-32px h-32px"
                        />
                        <div v-else class="flow-icon">
                          <span style="font-size: 12px; color: #fff">
                            {{ subString(definition.name, 0, 2) }}
                          </span>
                        </div>
                        <el-text class="!ml-10px" size="large" truncated
                          >{{ definition.name }}
                        </el-text>
                      </div>
                    </template>
                  </el-card>
                </el-tooltip>
              </div>
            </div>
          </el-scrollbar>
        </el-col>
      </el-row>
    </ContentWrap>
  </template>
  <ProcessStartForm
    v-else
    ref="processStartFormRef"
    :process-definition="startProcessDefinition"
    @cancel="startProcessDefinition = undefined"
  />
</template>

<script setup lang="ts">
import { ProcessDefinitionApi, ProcessDefinitionInfoVO } from '@/api/bpm/process_definition'
import { ProcessInstanceApi, ProcessInstanceVO } from '@/api/bpm/process_instance'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import { BpmModelFormTypeEnum, BpmModelStatusEnum, EnableStatusEnum } from '@/utils/constants'
import { groupBy } from 'lodash-es'
import ProcessStartForm from './ProcessStartForm.vue'
import { subString, s_file_url } from '@/utils/lizard'

defineOptions({ name: 'BpmProcessInstanceCreate' })

const route = useRoute() // 路由
const { push } = useRouter() // 路由
const message = useMessage() // 消息

const searchName = ref('')
const loading = ref(true) // 加载中
const categoryList = ref<CategoryVO[]>([])
const categoryActive = ref<CategoryVO>({} as any)
const processDefinitionList = ref<ProcessDefinitionInfoVO[]>([])

const getList = async () => {
  loading.value = true
  try {
    // 获取流程分类数据
    await getCategoryList()
    await getProcessDefinitionList()

    const processInstanceId: any = route.query.processInstanceId // 流程实例编号。场景：重新发起时
    if (processInstanceId?.length > 0) {
      const processInstance: ProcessInstanceVO =
        await ProcessInstanceApi.getProcessInstance(processInstanceId)
      if (!processInstance) {
        message.error('重新发起流程失败，原因：流程实例不存在')
        return
      }
      // 使用流程定义key匹配，因为可能该流程已经发布了新的流程定义
      const processDefinition = processDefinitionList.value.find(
        (definition) => definition.key == processInstance.processDefinition?.key
      )
      if (!processDefinition) {
        message.error('重新发起流程失败，原因：流程不存在')
        return
      }
      await handleStartProcess(processDefinition, processInstance.startFormVariables)
    }
  } finally {
    loading.value = false
  }
}

let processDefinitionListUpTime = 0
const getProcessDefinitionList = async () => {
  processDefinitionList.value = await ProcessDefinitionApi.getProcessDefinitionList(
    BpmModelStatusEnum.Active
  )
  processDefinitionListUpTime = new Date().getTime()
  // 初始化过滤列表为全部流程定义
  filteredProcessDefinitionList.value = processDefinitionList.value
  // 设置第一个可用分类为激活分类
  if (availableCategoryList.value.length && !categoryActive.value?.code) {
    categoryActive.value = availableCategoryList.value[0]
  }
}

const getCategoryList = async () => {
  categoryList.value = await CategoryApi.getCategorySimpleList(EnableStatusEnum.Enabled)
}

/** 搜索流程，过滤流程定义 */
const filteredProcessDefinitionList = ref<ProcessDefinitionInfoVO[]>([]) // 用于存储搜索过滤后的流程定义
const handleQuery = () => {
  if (searchName.value.trim()) {
    filteredProcessDefinitionList.value = processDefinitionList.value.filter(
      (definition: ProcessDefinitionInfoVO) => {
        return definition.name.toLowerCase().includes(searchName.value.toLowerCase())
      }
    )
  } else {
    filteredProcessDefinitionList.value = processDefinitionList.value
  }
}

/** 过滤后的流程定义分组数据，按照分类顺序组织，排除无流程定义的分组 */
const processDefinitionGroup = computed<any>(() => {
  if (!filteredProcessDefinitionList.value?.length) {
    return {}
  }
  const grouped = groupBy(filteredProcessDefinitionList.value, 'category')
  // 按照categoryList顺序组织数据
  const orderedGroup = {}
  categoryList.value.forEach((category: CategoryVO) => {
    if (grouped[category.code]) {
      // 分类无流程的不加入分组结果
      orderedGroup[category.code] = grouped[category.code]
    }
  })
  return orderedGroup
})

/** 可用的分类列表，经过查询过滤后的，排除其下无流程定义的分类 */
const availableCategoryList = computed(() => {
  if (!categoryList.value?.length) {
    return []
  }
  const availableCategoryCodes = Object.keys(processDefinitionGroup.value)
  return categoryList.value.filter((category: CategoryVO) =>
    availableCategoryCodes.includes(category.code)
  )
})

let categorySelectScrollTs = 0
const handleCategorySelect = (category: CategoryVO) => {
  categoryActive.value = category
  const categoryRef = proxy.$refs[`category-${category.code}`] // 获取点击分类对应的 DOM 元素
  if (categoryRef?.length) {
    const scrollWrapper = proxy.$refs.scrollWrapper // 获取右侧滚动容器
    const categoryOffsetTop = categoryRef[0].offsetTop
    // 滚动到对应位置
    categorySelectScrollTs = new Date().getTime()
    scrollWrapper.scrollTo({ top: categoryOffsetTop, behavior: 'smooth' })
  }
}

/** 通过分类 code 获取对应的名称 */
const getCategoryName = (categoryCode: any) => {
  return categoryList.value?.find((category: any) => category.code === categoryCode)?.name
}

const startProcessDefinition = ref() // 选择的流程定义
const processStartFormRef = ref<InstanceType<typeof ProcessStartForm>>()
const handleStartProcess = async (definition: ProcessDefinitionInfoVO, formVariables?: any) => {
  if (definition.formType === BpmModelFormTypeEnum.Normal) {
    startProcessDefinition.value = definition
    await nextTick()
    processStartFormRef.value?.initProcessInfo(definition, formVariables)
  } else if (definition.formCustomCreatePath) {
    await push({ path: definition.formCustomCreatePath })
  }
}

const { proxy } = getCurrentInstance() as any

const handleScroll = (e: any) => {
  // 消除分类点击导致的滚动对该函数的影响，若为点击分类的滚动，该函数不触发(阈值为800毫秒)
  if (new Date().getTime() - categorySelectScrollTs < 800) {
    return
  }

  // 直接使用事件对象获取滚动位置
  const scrollTop = e.scrollTop

  // 实时获取所有分类区域的位置信息(不能缓存，因为有页面有搜索功能，会动态调整页面流程定义元素)
  const categoryPositions = categoryList.value
    .map((category: CategoryVO) => {
      const categoryRef = proxy.$refs[`category-${category.code}`]
      if (categoryRef?.length) {
        return {
          category: category,
          offsetTop: categoryRef[0].offsetTop,
          height: categoryRef[0].offsetHeight
        }
      }
      return null
    })
    .filter((c) => c !== null)

  if (!categoryPositions?.length) {
    return
  }

  // 查找当前滚动位置对应的分类
  let currentCategory = categoryPositions[0]
  for (let i = categoryPositions.length - 1; i >= 0; i--) {
    const position = categoryPositions[i]
    if (scrollTop >= position!.offsetTop - 50) {
      currentCategory = position
      break
    }
  }

  // 更新当前 active 的分类
  if (currentCategory && categoryActive.value.code !== currentCategory?.category?.code) {
    categoryActive.value = currentCategory?.category
  }
}

const processListUpGap = 20 * 60 * 1000
const firstLoad = ref(true)
onActivated(() => { // keepalive组件才会启用，首次装载会在onMounted之后执行，后续从keepalive中被激活也会执行
  if (firstLoad.value) {  // 确保不与onMounted重复执行逻辑
    firstLoad.value = false
    return
  }
  // 超过指定时间也进行刷新，防止某些流程有新的发布
  if (history.state?.reload || (new Date().getTime() - processDefinitionListUpTime > processListUpGap)) {
    getList()
  }
})

/** 初始化 */
onMounted(() => {
  getList()
})
</script>

<style scoped lang="scss">
.flow-icon {
  display: flex;
  width: 32px;
  height: 32px;
  margin-right: 10px;
  background-color: var(--el-color-primary);
  border-radius: 0.25rem;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.process-definition-container::before {
  position: absolute;
  left: 20.8%;
  height: 100%;
  border-left: 1px solid #e6e6e6;
  content: '';
}

.process-definition-main {
  height: calc(
    100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-content-padding) - var(
        --app-footer-height
      ) -
      53px
  );
}

.main-scroll-area {
  height: calc(
    100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-content-padding) - var(
        --app-footer-height
      ) -
      53px
  );
}

:deep() {
  .definition-item-card {
    .el-card__body {
      padding: 14px;
    }
  }
}
</style>
